package asteroids.programs;

import java.util.*;

import be.kuleuven.cs.som.annotate.Basic;

import asteroids.Ship;
import asteroids.statements.*;
import asteroids.types.*;

public class Program {
	
	private Map<String, Type> globals;
	private Statement mainStatement;
	private List<Statement> statements;

	public Program(Map<String, Type> globals, Statement statement) {
		this.globals = globals;
		this.mainStatement = statement;
		setStatements(((Statement) this.getMainStatement()).getStatements());
	}

	@Basic
	public Map<String, Type> getGlobals() {
		return globals;
	}

	@Basic
	public Statement getMainStatement() {
		return mainStatement;
	}

	@Basic
	public List<Statement> getStatements() {
		return statements;
	}
	
	public void setStatements(List<Statement> statements) {
		this.statements = statements;
	}
	
	public List<String> getTypeCheckErrors() {
		List<String> typeErrors = new ArrayList<String>();
		return getMainStatement().getTypeCheckErrors(typeErrors, getGlobals());
	}
	
	public void executeProgram(Ship ship) {
		List<Statement> statementsToBeExecuted = new ArrayList<Statement>();
		List<Statement> postponedStatements = new ArrayList<Statement>();
		boolean actionStatementOccurred = false;
		boolean whileIfSequenceOccurred = false;
		
		for(int i = 0; i < getStatements().size(); i++) {
			Statement statement = getStatements().get(i);
			if(!actionStatementOccurred && !whileIfSequenceOccurred) {
				if(statement instanceof SequenceStatement) {
					postponedStatements.addAll(((SequenceStatement) statement).getSubStatements());
				whileIfSequenceOccurred = true;
				}
				else if(statement instanceof ActionStatement) {
					statementsToBeExecuted.add(statement);
					actionStatementOccurred = true;
				}
				else if(statement instanceof WhileLoopStatement && (boolean) ((WhileLoopStatement) statement).getCondition().evaluate(ship, this.getGlobals()).getValue()) {
					//In nextStatementList (and not in statementsToBeExecuted) to avoid executing the 
					//whole WhileLoopStatement (doesn't stop when ActionStatement occurs).
					postponedStatements.addAll(((WhileLoopStatement) statement).getBody().getStatements());
					postponedStatements.add(statement);
					whileIfSequenceOccurred = true;
				}
				else if(statement instanceof IfThenElseStatement) {
					//For same reason in nextStatementList as above.
					if((boolean) ((IfThenElseStatement) statement).getCondition().evaluate(ship, this.getGlobals()).getValue()) {
						postponedStatements.addAll(((IfThenElseStatement) statement).getIfLoop().getStatements());
					}
					else {
						postponedStatements.addAll(((IfThenElseStatement) statement).getElseLoop().getStatements());
					}
					whileIfSequenceOccurred = true;
				}
				else {
					//BasicStatement and ForEachStatements can be added in the statementsToBeExecutedList, because they cannot contain ActionStatement.
					statementsToBeExecuted.add(statement);
				}
			}
			else {
				postponedStatements.add(statement);
			}
		}
		for(Statement statementToExecute: statementsToBeExecuted){
			statementToExecute.execute(ship);
		}
		
		this.setStatements(postponedStatements);
		if(whileIfSequenceOccurred)
			executeProgram(ship);
		
		if(statementsToBeExecuted.isEmpty() && postponedStatements.isEmpty())
			//Set the initial statements of the program back in the list. 
			setStatements(((Statement) this.getMainStatement()).getStatements());
	}
		
}


